# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Tree data store and Abstract chooser for selecting an item using tile/tk.
"""
__docformat__ = 'restructuredtext en'
_ = lambda x: x

import ttk
import tkSimpleDialog
from Tkinter import DoubleVar, BooleanVar
from operator import itemgetter

from .canvas import ImageItem
from . import constants as vcnst
from .dialog import Dialog
from ..gamelib.data import GAME_DB
from ..gamelib import group as GI
from ..gamelib.info import GT
from ..utillib.misc import format_time
from ..packlib import info as CSI
from ..packlib import group as CSG
from ..packlib.data import CARD_DB
from ..packlib.info import verify_cardset
from ..packlib.image import Images
from ..resourceng import TILE_DB
from ..settings import CONF_OPT
from ..statdb import STAT_DB
from ..utillib.loadhelp import load_help
from .window import PAD

PANED_OPTIONS = {'fill': vcnst.BOTH, 'padx': 2 * PAD.x, 'pady': 2 * PAD.y,
                   'expand': True}

class TreeviewMixin(ttk.Treeview, object):
    """
    Mixin to manage a ttk tree view.
    """

    def __init__(self, parent, width):
        """
        Initialize a tree view specific to creating a tree list view.

        .. \__init__(parent:Widget, width:int)
        """
        super(TreeviewMixin, self).__init__(parent, columns=('identity',),
                                      displaycolumns='', selectmode='browse',
                                      show='tree')
        self.column('#0', width=width)

    def _create_node(self, parent, label, value=None):
        """
        Create a node item.

        Return next index after being appended.

        .. \_create_node(parent:Node_ID, label:str[, value:int=-1]) -> Node_ID
        """
        kwargs = {}
        if value:
            kwargs = {'values': (value,), 'tags': ('item')}
        return self.insert(parent, 'end', text=label, **kwargs)

    def _select(self, func):
        """
        Get filtered sequence by given func.

        Return sequence of id-name tuples based on given func evaluation from
        an overall list. Stub for subclass to determine values to include.

        .. \_select(func) -> data:seq(seq(id, str) ...)
        """
        pass

    def _create_branch(self, parent, label, data):
        """
        Create branch of node items.

        Append branch node with its sub nodes.

        .. \_create_branch(parent:Node, label:str, data:seq(seq(str, str) ...))
             -> None
        """
        node = self._create_node(parent, label)
        for cid, name in data:
            self._create_node(node, name, value=cid)


class ViewSelectorMixin(tkSimpleDialog.Dialog, object):
    """
    Abstract Chooser with preview
    """
    result = False
    __app = None
    __labels = {}
    _pick = None
    _treestore = None

    def __init__(self, parent, title, app, choice, valid=None):
        """
        Create a new App object for previews from **app** value. Sets preview
        from **choice** value. Call its parent method in **Dialog**.

        .. \__init__(parent:Canvas, title:str, app:App, choice:int[, valid:int])
             -> None
        """
        from ..__init__ import PysolApp
        app = type('PreviewApp', (PysolApp,), app.__dict__)
        self.__app = app()
        self._pick = choice
        self._valid = valid  # used by card chooser to validate pick for game.
        super(ViewSelectorMixin, self).__init__(parent, title)

    def deiconify(self):
        """
        Deiconify dialog window.

        Augment call update preview method after window mapped. Important so
        dialog does not lag on a preview update delaying the window mapping.

        .. deiconify() -> None
        """
        super(ViewSelectorMixin, self).deiconify()
        self._update_preview()

    @property
    def _preview_canvas(self):
        """
        Return current preview canvas.

        .. \_preview_canvas -> Canvas
        """
        return None if not self.__app else self.__app.canvas

    @property
    def _preview_app(self):
        """
        Return current preview app.

        .. \_preview_app -> Canvas
        """
        return None if not self.__app else  self.__app

    def body(self, parent):
        """
        Create dialog body.

        Stub to subclass creation of the view selector body.

        .. body(parent:Canvas) -> None
        """
        raise NotImplementedError

    def _create_tree_pane(self, parent):
        """
        Create selector for dialog.

        Create a selector with available choices sorted into various groups as
        determined by tree store value.

        .. \_create_tree_pane(parent:Canvas) -> None
        """
        assert (self._treestore is not None and
                issubclass(self._treestore, TreeviewMixin))
        store = self._treestore(parent, width=240)
        store.grid(row=0, column=0, sticky=vcnst.STICKY_NSEW)
        store.tag_bind('item', '<<TreeviewSelect>>', self._selected_item)
        ysb = ttk.Scrollbar(parent, orient=vcnst.VERTICAL, command=store.yview)
        store['yscroll'] = ysb.set
        ysb.grid(row=0, column=1, sticky=vcnst.STICKY_NS)
        parent.columnconfigure(0, weight=1)
        parent.rowconfigure(0, weight=1)

    def _selected_item(self, event):
        """
        Set preview to selector choice.

        .. \_selected_item(event) -> None
        """
        store = event.widget
        self._update_preview(store.item(store.focus(), 'values')[0])

    def _create_preview_pane(self, parent):
        """
        Create canvas preview for selector choice.

        .. \_create_preview_pane(parent:Canvas) -> None
        """
        from .toolkit import Canvas
        preview = type('PreviewCanvas', (Canvas,),{})
        self.__app.canvas = preview(parent, width=480, height=240)
        self.__app.canvas.set_background(TILE_DB.choice(), force=True)
        #self.__app.canvas = preview

    def _delete_preview(self):
        """
        Reset canvas preview for next selector choice.

        .. \_delete_preview() -> None
        """
        if self.__app:
            self.__app.canvas.reset()

    def _update_preview(self, choice=None, force=False):
        """
        Set selector choice on canvas preview.

        Stub to preview a new selector choice.

        .. \_update_preview(choice:int=self._pick, force:bool=False) -> None
        """
        raise NotImplementedError

    def _create_label_frame(self, parent, title, fields):
        """
        Create details text for selector choice.

        Create a frame with a given title and fields as row labels. Given fields
        should be a sequence of keys and their field titles. Field values are
        updated with update label frame and use the keys to hide empty values.

        .. \_create_label_row(parent:Canvas, title:str,
             fields:seq(seq(str, str) ...)) -> None
        """
        iframe = ttk.LabelFrame(parent, text=title, width=240)
        iframe.pack(fill=vcnst.BOTH, expand=True, side=vcnst.LEFT)

        for row, (key, title) in enumerate(fields):
            title_label = ttk.Label(iframe, text=title, anchor=vcnst.ANCHOR_W)
            title_label.grid(row=row, column=0, sticky=vcnst.STICKY_EW,
                             padx=PAD.x)
            title_label.columnconfigure(0, weight=1)
            text_label = ttk.Label(iframe, anchor=vcnst.ANCHOR_W)
            text_label.grid(row=row, column=1, sticky=vcnst.STICKY_EW,
                            padx=PAD.x)
            text_label.columnconfigure(1, weight=1)
            self.__labels[key] = (title_label, text_label)

    def update_label_frame(self, fields):
        """
        Update details text for selector choice.

        Update frame labels with fields data. Given fields are a sequence of
        keys and their field values.

        .. \_update_label_frame(fields:seq(seq(str, str) ...)) -> None
        """
        for key, value in fields:
            title_label, text_label = self.__labels[key]
            if value in ('', None):
                title_label.grid_remove()
                text_label.grid_remove()
            else:
                title_label.grid()
                text_label.grid()
            text_label.config(text=value)

    aux_button = lambda s, f: None

    def buttonbox(self):
        """
        Create buttons for selector.

        Overide standard buttons with buttons for choosing or abandoning the
        selector choice.

        .. buttonbox() -> None
        """
        box = ttk.Frame(self)
        widget = ttk.Button(box, text=_('Select'), style='npsDialog.TButton',
                                        command=self.ok, default=vcnst.ACTIVE)
        widget.pack(side=vcnst.LEFT, padx=PAD.x, pady=PAD.y)
        widget = ttk.Button(box, text=_('Cancel'), style='npsDialog.TButton',
                            command=self.cancel)
        widget.pack(side=vcnst.LEFT, padx=PAD.x, pady=PAD.y)
        self._aux_button = self.aux_button(box)
        if isinstance(self._aux_button, ttk.Button):
            self._aux_button['style'] = 'npsDialog.TButton'
            self._aux_button.pack(side=vcnst.LEFT, padx=2 * PAD.x, pady=PAD.y)
        self.bind('<Return>', self.ok)
        self.bind('<Escape>', self.cancel)
        box.pack()

    def destroy(self):
        """
        Destroy selector view.

        .. destroy() -> None
        """
        self._delete_preview()
        self.__app.canvas.delete('all')
        self.__app = None
        super(ViewSelectorMixin, self).destroy()

    def apply(self):
        """
        Set result to last selector choice.

        .. apply() -> None
        """
        self.result = self._pick


class CardTreeview(TreeviewMixin):
    """
    Manage a card selector tree view.
    """

    def __init__(self, parent, width):
        """
        Init card selector.

        Create set branches from available packs based on card group selectors.

        .. \__init__() -> None
        """
        super(CardTreeview, self).__init__(parent, width)
        self.__cards = tuple((v['index'], v) for v in CARD_DB.sort('name'))

        def branch(label, data, func, nofunc=None, parent='',):
            """
            Build branches on parent with label based on data.
            """
            if func is None:
                self._create_branch(parent, label, self._select(func))
            else:
                node = self._create_node(parent, label)
                for id_, label in sorted(data.items(), key=itemgetter(1)):
                    self._create_branch(node, label,
                                      self._select(lambda o, k=id_: func(o, k)))
            if nofunc:
                self._create_branch(node, _('Uncategorized'),
                                   self._select(nofunc))

        branch(_('All Cardsets'), None, None)
        branch(_('by Size'), CSG.SIZE,
            lambda cs, key: key == cs['si']['size'])
        branch(_('by Type'), CSG.TYPE,
            lambda cs, key: key == cs['si']['type'])
        branch(_('by Style'), CSG.STYLE,
            lambda cs, key: key in cs['si']['styles'],
            lambda cs: not cs['si']['styles'])
        branch(_('by Nationality'), CSG.NATIONALITY,
            lambda cs, key: key in cs['si']['nationalities'],
            lambda cs: not cs['si']['nationalities'])
        branch(_('by Date'), CSG.DATE,
            lambda cs, key: key in cs['si']['dates'],
            lambda cs: not cs['si']['dates'])

    def _select(self, func):
        """
        Get filtered sequence by given func.

        Return sequence of id-name tuples based on given func evaluation from
        an overall list. Filters CARD_DB into branches of a group type.
        """
        if func is None:
            return tuple((i, ci['name'],) for i, ci in self.__cards)
        else:
            return tuple((i, ci['name'],) for i, ci in self.__cards if func(ci))


class CardsetChooser(ViewSelectorMixin):
    """
    Card Chooser with preview
    """
    __vars = {}
    _treestore = CardTreeview

    def body(self, parent):
        """
        body(parent:Canvas) -> None

        Create a dialog with a card chooser on the left and a card preview on
        the right. Below the card chooser, scaling details are shown.
        """
        paned_window = ttk.PanedWindow(parent, orient='horizontal')

        # Left side:
        left_frame = ttk.Frame(paned_window)
        self._create_tree_pane(left_frame)
        self._scale_pane(left_frame)
        left_frame.pack(fill=vcnst.BOTH, expand=True)
        paned_window.add(left_frame)

        # Right side:
        right_frame = ttk.Frame(paned_window)
        self._create_preview_pane(right_frame)
        right_frame.pack(fill=vcnst.BOTH, expand=True)
        paned_window.add(right_frame)

        paned_window.pack(**PANED_OPTIONS)
        parent.pack(**PANED_OPTIONS)

    def _scale_pane(self, parent):
        """
        Create scaling widgets to set card resize behavior.

        .. \_scale_pane(parent:Widget) -> None
        """

        def create_scale(frame, label, key, row):
            """
            create_scale(label:str, key:str)
            """
            var = DoubleVar()
            var.set(CONF_OPT[key] * 10)
            self.__vars[key] = var
            widget = ttk.Label(frame, text='{}: 0%'.format(label))
            widget.grid(row=row, column=0)
            scale = ttk.Scale(frame, from_=1, to=20, variable=var,
                             orient=vcnst.HORIZONTAL, value=var.get(),
                             command=lambda s: self._update_preview(force=True))
            scale.grid(row=row, column=1, sticky=vcnst.STICKY_EW)
            widget = ttk.Label(frame, text=_(' 200%'))
            widget.grid(row=row, column=2)
            return scale

        def create_check(frame, label, key):
            """
            create_check(label:str, key:str)
            """
            self.__vars[key] = var = BooleanVar()
            var.set(CONF_OPT[key])
            check = ttk.Checkbutton(frame, text=label, variable=var,
                                   takefocus=False)
            check.pack(side=vcnst.LEFT, expand=True)
            return check

        # Scale widgets for setting a scale x and y coords
        iframe = ttk.LabelFrame(parent, text=_('Scale'))
        iframe.grid(row=1, column=0, sticky=vcnst.STICKY_NSEW)
        h_widget = create_scale(iframe, _('Horz.'), key='scale_x', row=0)
        v_widget = create_scale(iframe, _('Vert.'), key='scale_y', row=1)
        iframe.columnconfigure(1, weight=1)

        # Check widgets for setting auto scale and keep same aspect ratio.
        iframe = ttk.Frame(parent)
        iframe.grid(row=2, column=0, sticky=vcnst.STICKY_NSEW)
        a_widget = create_check(iframe, _('Auto scale:'), 'auto_scale')
        k_widget = create_check(iframe, _('Keep aspect:'),
                                'preserve_aspect_ratio')

        a_widget.config(command=lambda s, x=h_widget, y=v_widget, k=k_widget:
                                    self._update_scale(x, y, k))
        self._update_scale(h_widget, v_widget, k_widget)

    def _update_scale(self, scale_x, scale_y, keep_aspect):
        """
        Update scaling to enable preserving aspect and disabling horz and vert
        scale when auto scale. Update scaling to disable preserving aspect and
        enabling horz and vert scale when not auto scale.

        .. \_update_scale(scale_x:int, scale_y:int, keep_aspect:widget) -> None
        """
        tkvars = self.__vars
        if tkvars['auto_scale'].get():
            keep_aspect.config(state='normal')
            scale_x.state(statespec=('disabled',))
            scale_y.state(statespec=('disabled',))
        else:
            keep_aspect.config(state='disabled')
            scale_x.state(statespec=('!disabled',))
            scale_y.state(statespec=('!disabled',))

    def _update_preview(self, choice=None, force=False):
        """
        Update card preview with **choice** value when changed. When **force**
        value, resize card preview regardless of update change.

        .. \_update_preview([choice:int=*pick*, force:bool=False]) -> None
        """
        assert self._preview_canvas is not None

        if choice == self._pick and not force:
            return

        self._delete_preview()
        self._pick = choice or self._pick

        # Load and resize images for cardset selection
        images = Images(CARD_DB[self._pick])
        cardset = images.detail
        self.wm_title(_('Cardset Preview - {0[name]}').format(cardset))
        assert images.load()

        tkvars = self.__vars
        xscale = float('{0:.2f}'.format(tkvars['scale_x'].get() / 10))
        yscale = float('{0:.2f}'.format(tkvars['scale_y'].get() / 10))
        images.resize(xscale, yscale)

        # Place images on canvas
        pad, col = 10, 4
        xcoord, ycoord = pad, pad
        xdelta, ydelta = [s + pad for s in images.size]
        canvas = self._preview_canvas
        for index, card in enumerate(images.sample_faces_iter):
            ImageItem(canvas, (xcoord, ycoord,), image=card)
            if index % col == col - 1:
                xcoord = pad
                ycoord += ydelta
            else:
                xcoord += xdelta

        xcoord, ycoord = xdelta * col + pad * 2, ycoord + pad
        canvas.config(scrollregion=(0, 0, xcoord, ycoord), width=xcoord,
                      height=ycoord)
        canvas.event_generate('<Configure>')

    def validate(self):
        """
        Check cardset pick is a valid choice for game.

        .. validate() -> bool
        """
        if not verify_cardset(self._pick, self._valid):
            Dialog.warning(text=_('The current {} {} is not compatible.'
                             ).format(CSI.CARDSET, CARD_DB[self._pick]['name']))
            return False
        return True

    def apply(self):
        """
        Set card set choice to current card set. Set current scale settings.

        .. apply() -> None
        """
        self.result = True
        CARD_DB.choice(self._pick)

        tkvars = self.__vars

        CONF_OPT['auto_scale'] = bool(tkvars['auto_scale'].get())
        if CONF_OPT['auto_scale']:
            CONF_OPT['preserve_aspect_ratio'] = bool(
                                      tkvars['preserve_aspect_ratio'].get())
        else:
            CONF_OPT['scale_x'] = float(tkvars['scale_x'].get()) / 10
            CONF_OPT['scale_y'] = float(tkvars['scale_y'].get()) / 10


class GameTreeview(TreeviewMixin):
    """
    Manage a ttk Store (TreeStore) for tilesets.

    Creates a Store with games sorted into various groups.
    """

    def __init__(self, parent, width):
        """
        Create set of nodes with branches from **all_games** based on selectors
        from various groups.

        __init__() -> None
        """
        super(GameTreeview, self).__init__(parent, width)
        self.__games = tuple([GAME_DB[i] for i in GAME_DB.sort('name')])

        def branch(label, data, parent=''):
            """
            Build several branches of nodes on parent with label based on data.
            """
            if data is None:
                self._create_branch(parent, label, self._select(None))
            else:
                node = parent
                if label is not None:
                    node = self._create_node(parent, label)
                for label, func in sorted(data, key=itemgetter(0)):
                    self._create_branch(node, label, self._select(func))

        def altnames_branch(label, parent=''):
            """
            Build branch on parent for all alternate game names for a game.
            """
            data = []
            for index in GAME_DB.sort('alt_name'):
                gameinfo = GAME_DB[index]
                data.extend([gameinfo['id'], n] for n in gameinfo['altnames'])
            self._create_branch(parent, label, sorted(data, key=itemgetter(1)))

        node = self._create_node
        branch(_('All Games'), None)
        altnames_branch(_('Alternate Names'))
        branch(None, [(_('Popular Games'),
                          lambda gi: gi['si']['game_flags'] & GT.POPULAR)])
        branch(_('French games'), GI.GAMES_BY_TYPE)
        branch(None, [(_('Mahjongg Games'),
                          lambda gi: gi['si']['game_type'] == GT.MAHJONGG)])
        branch(_('Oriental Games'), GI.GAMES_ORIENT_BY_TYPE)
        branch(_('Special Games'), GI.GAMES_OTHERS_BY_TYPE)
        branch(None, [(_('Custom Games'),
                          lambda gi: gi['si']['game_type'] == GT.CUSTOM)])
        branch(_('by Skill Level'), GI.GAMES_BY_SKILL)
        snode = node('', _('by Game Feature'))
        branch(_('by Number of Cards'), GI.GAMES_BY_NCARDS, snode)
        branch(_('by Number of Decks'), GI.GAMES_BY_NDECKS, snode)
        branch(_('by Number of Redeals'), GI.GAMES_BY_NDEALS, snode)
        branch(_('by Compatibility'), GI.GAMES_BY_SIMILAR, snode)
        branch(_('by PySol version'), GI.GAMES_BY_VERSION)
        branch(_('by Inventors'), GI.GAMES_BY_INVENTORS)
        branch(_('Other Categories'), GI.GAME_BY_GROUPS)
        branch(None, [(_('Original Games'),
                          lambda gi: gi['si']['game_flags'] & GT.ORIGINAL)])
        branch(_('Submitted Games'), GI.GAMES_SUBMIT_BY_TYPE)

    def _select(self, func):
        """
        Returns sequence of (index, name) tuples based on **func** value to
        form a node with its of group backgrounds from the overall **tiles**.

        _select(func) -> seq(seq(id, str) ...)
        """
        if func is None:
            return tuple((gi['id'], gi['name'],) for gi in self.__games)
        else:
            return tuple((gi['id'], gi['name'],)
                         for gi in self.__games if func(gi))


class GameChooser(ViewSelectorMixin):
    """
    Game Chooser with preview
    """
    _treestore = GameTreeview
    __game = None
    _pick = -1
    gameid = None
    random = None

    def body(self, parent):
        """
        body(parent:Canvas) -> None

        Create a dialog with a game chooser on the left and a game preview on
        the right. Above the game preview, basic game and stat details is shown.
        """
        paned_window = ttk.PanedWindow(parent, orient='horizontal')

        # Left side:
        left_frame = ttk.Frame(paned_window)
        self._create_tree_pane(left_frame)
        left_frame.pack(fill=vcnst.BOTH, expand=True)
        paned_window.add(left_frame)

        # Right side:
        right_frame = ttk.Frame(paned_window)
        info_frame = ttk.Frame(right_frame)
        self._create_label_data(info_frame)
        info_frame.pack(fill=vcnst.BOTH, expand=True)
        self._create_preview_pane(right_frame)
        right_frame.pack(fill=vcnst.BOTH, expand=True)
        paned_window.add(right_frame)

        paned_window.pack(**PANED_OPTIONS)
        parent.pack(**PANED_OPTIONS)

    def aux_button(self, frame):
        """
        Overide button for game assist. Set game assist state when rules exist.

        .. aux_button(frame:Widget) -> None
        """
        def command(self):
            """
            Handle rules button by call load help method for rules filename.
            """
            info = GAME_DB[self._pick]
            if info is not None:
                load_help(self.__app, info.rules_filename)

        return ttk.Button(frame, text=_('Rules…'), command=command)

    def _create_label_data(self, parent):
        """
        Set fields for basic game and stat info. Call label creation with data
        with titles.

        .. \_create_label_data(parent:Canvas) -> None
        """
        fields = (('name', _('Name:'),), ('altnames', _('Alternate names:'),),
                  ('category', _('Category:'),), ('type', _('Type:'),),
                  ('skill', _('Skill level:'),), ('decks', _('Decks:'),),
                  ('redeals', _('Redeals:'),),)
        self._create_label_frame(parent, _('Details'), fields)

        fields = (('played', _('Played:'),), ('won', _('Won:'),),
                  ('lost', _('Lost:'),), ('time', _('Playing time:'),),
                  ('moves', _('Moves:'),), ('percent', _('% won:'),),)
        self._create_label_frame(parent, _('Summary'), fields)

        self.update_label_data()

    def update_label_data(self):
        """
        Update labels with basic game info and stat info for game preview. Call
        label update with data values.

        .. \_update_label_data() -> None
        """
        lgi = GAME_DB[GAME_DB.find(self._pick, 'id')]

        fields = (('name', lgi['name']),
                  ('altnames', '\n'.join(lgi['altnames'])),
                  ('category', _(CSG.TYPE[lgi['category']])),
                  ('type', [s[0] for s in GI.GAMES_BY_TYPE if s[1](lgi)][0]),
                  ('skill', [s[0] for s in GI.GAMES_BY_SKILL if s[1](lgi)][0]),
                  ('decks', lgi['decks']),
                  ('redeals', {-2:  _('variable'), -1: _('unlimited')
                              }.get(lgi['redeals'], str(lgi['redeals']))),)
        self.update_label_frame(fields)

        won, lost, time, moves = STAT_DB.full(CONF_OPT['player'], lgi['id'])
        total = won + lost
        fields = (('played', total), ('won', won), ('lost', lost),
                 ('time', format_time(time)), ('moves', '{:.2f}'.format(moves)),
                 ('percent', '{:.2%}'.format(won / total if total > 0 else 0)),)
        self.update_label_frame(fields)

    def _delete_preview(self):
        """
        Destroy preview game and clean up canvas.

        .. \_delete_preview() -> None
        """
        if self.__game:
            self.random = self.__game.random
#            self.__game.destruct()
        self.__game = None

        ViewSelectorMixin._delete_preview(self)

    def _update_preview(self, choice=None, force=False):
        """
        Update game preview to init a new game when a new selection is made.

        .. \_update_preview_pane(choice:int=self._pick, force:bool=False)
             -> None
        """
        assert self._preview_app is not None
        if choice == self._pick and not force:
            return

        self._delete_preview()

        # Prepare game preview
        self._pick = choice or self._pick
        self.update_label_data()
        preview_app = self._preview_app
        self.__game = preview_app.construct_game(self._pick)
        self.wm_title(_('Playable Preview - {0[name]}').format(
                                                             self.__game))
        self.__game.prepare(preview_app, preview=True)

        #Update rules button
        if hasattr(self, 'assist_button'):
            info = self.__game.gameinfo.get('rules_filename')
            self._aux_button.config(state='normal' if info else 'disabled')
        self.__game.new()

    def apply(self):
        """
        Set **self.gameid** to current game. Save chooser expansions.

        .. apply() -> None
        """
        self.results = self._pick


class TileTreeview(TreeviewMixin):
    """
    Manage a tile / color selector tree view.
    """

    def __init__(self, parent, width):
        """
        Create set of branches from tile db based on various group selectors.

        .. \__init__(parent:Widget, width:int) -> None
        """
        super(TileTreeview, self).__init__(parent, width)
        self.__tiles = tuple((v['index'], v) for v in TILE_DB.sort('name'))

        def branch(label, data, func, parent=''):
            """
            Build branches on parent with label based on data.
            """
            if func is None:
                self._create_branch(parent, label, self._select(func))
            else:
                node = self._create_node(parent, label)
                for id_, label in sorted(data.items(), key=itemgetter(1)):
                    self._create_branch(node, label,
                                      self._select(lambda o, k=id_: func(o, k)))

        node = self._create_node
        # Add 16 solid web colors defined by HTML spec.
        snode = node('', _('Solid Colors'))
        node(snode, _('White'), value='#FFFFFF')
        node(snode, _('Silver'), value='#C0C0C0')
        node(snode, _('Gray'), value='#808080')
        node(snode, _('Black'), value='#000000')
        node(snode, _('Red'), value='#FF0000')
        node(snode, _('Maroon'), value='#800000')
        node(snode, _('Yellow'), value='#FFFF00')
        node(snode, _('Olive'), value='#808000')  # was #868200
        node(snode, _('Lime'), value='#00FF00')
        node(snode, _('Green'), value='#008000')  # was #008200
        node(snode, _('Aqua'), value='#00FFFF')
        node(snode, _('Teal'), value='#008080')  # was #008286
        node(snode, _('Blue'), value='#0000FF')  # was #0082df
        node(snode, _('Navy'), value='#000080')  # was #000086
        node(snode, _('Fuchsia'), value='#FF00FF')
        node(snode, _('Purple'), value='#800080')
        branch(_('All Backgrounds'), None, None)

    def _select(self, func):
        """
        Returns sequence of index and name tuples based on given func value.

        .. \_select(func) -> data:seq(seq(id, str) ...)
        """
        if func is None:
            return tuple((i, ti['name'],) for i, ti in self.__tiles)
        else:
            return tuple((i, ti['name'],) for i, ti in self.__tiles if func(ti))


class TilesetChooser(ViewSelectorMixin):
    """
    Tile Chooser with preview
    """
    _color = None
    _treestore = TileTreeview

    def __init__(self, parent, title, app, choice):
        """
        Modify given choice value when value 0 to set config option color value.
        Call next method in order.

        .. \__init__(parent:Widget, title:str, app:App, choice:int) -> None
        """
        choice = choice if choice > 0 else CONF_OPT['colors']['table']
        self._color = CONF_OPT['colors']['table']
        super(TilesetChooser, self).__init__(parent, title, app, choice)

    def body(self, parent):
        """
        Create a background selector on the left and a preview on the right.

        .. body(parent:Widget) -> None
        """
        paned_window = ttk.PanedWindow(parent, orient=vcnst.HORIZONTAL)

        # Left side:
        left_frame = ttk.Frame(paned_window)
        self._create_tree_pane(left_frame)
        left_frame.pack(fill=vcnst.BOTH, expand=True)
        paned_window.add(left_frame)

        # Right side:
        right_frame = ttk.Frame(paned_window)
        self._create_preview_pane(right_frame)
        right_frame.pack(fill=vcnst.BOTH, expand=True)
        paned_window.add(right_frame)

        paned_window.pack(**PANED_OPTIONS)
        parent.pack(**PANED_OPTIONS)

    def update_preview(self, choice=None, force=False):
        """
        Update canvas preview with selector choice when changed.

        .. update_preview(choice:int=self._pick, force:bool=False) -> None
        """
        assert self._preview_canvas is not None
        if choice == self._pick and not force:
            return

        self._delete_preview()
        choice = choice or self._pick

        # Update tileset or color on canvas preview
        canvas = self._preview_canvas
        if str(choice).isdigit():
            self._pick = choice = int(choice)
            canvas.set_background(choice)
            self.wm_title(_('Tileset Image Preview - {0[name]}'
                            ).format(TILE_DB[choice]))
        else:
            canvas.set_background(0)
            canvas.config(bg=choice)
            self.wm_title(_('Tileset Color Preview - {0}').format(choice))
            self._color = choice
            self._pick = choice

    def aux_button(self, frame):
        """
        Overide button for background color to select abitrary color.

        .. aux_button(frame:Widget) -> None
        """
        def command(self):
            """
            Handle color button by call ask color method for for new color.
            """
            from tkColorChooser import askcolor
            chooser = askcolor(master=self, initialcolor=self._color,
                               title=_('Choose table color'))
            if chooser and chooser[1]:
                self.update_preview(str(chooser[1]))

        return ttk.Button(frame, text=_(u'Color…'), command=command)

    def apply(self):
        """
        Set choice of background tileset or color.

        .. apply() -> None
        """
        self.result = True
        CONF_OPT['colors']['table'] = self._color
        if not isinstance(self._pick, int):
            TILE_DB[0]['color'] = self._color
            self._pick = 0
        if self._pick != TILE_DB.choice():
            TILE_DB.choice(self._pick)